<?php

namespace App\Http\Controllers\Api;

use Illuminate\Http\Request;
use App\Http\Controllers\Controller;
use Illuminate\Support\Facades\DB;


class PayOrderController extends Controller{

    private $app_id; // 微信支付配置
    private $app_secret; // 微信支付配置
    private $mchid; // 微信支付配置
    private $apikey; // 微信支付配置

    /**
     * 构造函数
     * WxPay constructor.
     * @param $config
     */

    public function __construct(){
        $list=DB::table('wxapp')->first();
        $this->app_id=$list->app_id;
        $this->app_secret=$list->app_secret;
        $this->mchid=$list->mchid;
        $this->apikey=$list->apikey;

    }

    /**
     * 统一下单API
     * @param $order_no
     * @param $openid
     * @param $total_fee
     * @return array
     * @throws BaseException
     */
    public function unifiedorder($order_no, $openid, $total_fee){
        // 当前时间
        $time = time();
        // 生成随机字符串
        $nonceStr = md5($time . $openid);
        // API参数
        $params = [
            'appid' => $this->app_id,
            'attach' => 'test',
            'body' => $order_no,
            'mch_id' => $this->mchid,
            'nonce_str' => $nonceStr,
            'notify_url' =>$_SERVER['HTTP_HOST'].'/api/notifyorder',  // 异步通知地址
            'openid' => $openid,
            'out_trade_no' => $order_no,
            'spbill_create_ip' =>$_SERVER["REMOTE_ADDR"],
            'total_fee' => $total_fee * 100, // 价格:单位分
            'trade_type' => 'JSAPI',
        ];

        // 生成签名
        $params['sign'] = $this->makeSign($params);
        // 请求API
        $url = 'https://api.mch.weixin.qq.com/pay/unifiedorder';
        $result = $this->post($url, $this->toXml($params));

        $prepay = $this->fromXml($result);
        
        // 请求失败
        if ($prepay['return_code'] === 'FAIL') {
            return ['msg' => $prepay['return_msg'], 'code' => -10];
        }
        if ($prepay['result_code'] === 'FAIL') {
            return ['msg' => $prepay['return_msg'], 'code' => -10];
        }
        // 生成 nonce_str 供前端使用
        $paySign = $this->makePaySign($params['nonce_str'], $prepay['prepay_id'], $time);

         return [
            'prepay_id' => $prepay['prepay_id'],
            'nonceStr' => $nonceStr,
            'timeStamp' => (string)$time,
            'paySign' => $paySign
        ];
    }


    /**
     * 支付成功异步通知
     * @param \app\task\model\Order $OrderModel
     * @throws BaseException
     * @throws \Exception
     * @throws \think\exception\DbException
     */
    public function notify(){
        if (!$xml = file_get_contents('php://input')) {
            $this->returnCode(false, 'Not found DATA');
        }
        // 将服务器返回的XML数据转化为数组
        $data = $this->fromXml($xml);
        // 保存微信服务器返回的签名sign
        $dataSign = $data['sign'];
        // sign不参与签名算法
        unset($data['sign']);
        // 生成签名
        $sign = $this->makeSign($data);

        // 判断签名是否正确  判断支付状态
        if (($sign === $dataSign)
            && ($data['return_code'] == 'SUCCESS')
            && ($data['result_code'] == 'SUCCESS')) {
            // 订单支付成功业务处理
            $update['pay_status']=20;
            $update['pay_time']=time();
            DB::table('order')->where('all_order_no',$data['out_trade_no'])->update($update);
            // 返回状态
            $this->returnCode(true, 'OK');
        }
        // 返回状态
        $this->returnCode(false, '签名失败');
    }


    /**
     * 申请退款API
     * @param $transaction_id
     * @param  double $total_fee 订单总金额
     * @param  double $refund_fee 退款金额
     * @return bool
     * @throws BaseException
     */
//    public function refund($transaction_id, $total_fee, $refund_fee)
//    {
//        // 当前时间
//        $time = time();
//        // 生成随机字符串
//        $nonceStr = md5($time . $transaction_id . $total_fee . $refund_fee);
//        // API参数
//        $params = [
//            'appid' => $this->appId,
//            'mch_id' => $this->config['mchid'],
//            'nonce_str' => $nonceStr,
//            'transaction_id' => $transaction_id,
//            'out_refund_no' => $time,
//            'total_fee' => $total_fee * 100,
//            'refund_fee' => $refund_fee * 100,
//        ];
//        // 生成签名
//        $params['sign'] = $this->makeSign($params);
//        // 请求API
//        $url = 'https://api.mch.weixin.qq.com/secapi/pay/refund';
//        $result = $this->post($url, $this->toXml($params), true, $this->getCertPem());
//        $prepay = $this->fromXml($result);
//        // 请求失败
//        if ($prepay['return_code'] === 'FAIL') {
//            throw new BaseException(['msg' => 'return_msg: ' . $prepay['return_msg']]);
//        }
//        if ($prepay['result_code'] === 'FAIL') {
//            throw new BaseException(['msg' => 'err_code_des: ' . $prepay['err_code_des']]);
//        }
//        return true;
//    }

    /**
     * 获取cert证书文件
     * @return array
     * @throws BaseException
     */
    private function getCertPem()
    {
        if (empty($this->config['cert_pem']) || empty($this->config['key_pem'])) {
            throw new BaseException(['msg' => '请先到后台小程序设置填写微信支付证书文件']);
        }
        // cert目录
        $filePath = __DIR__ . '/cert/' . $this->config['wxapp_id'] . '/';
        !is_dir($filePath) && mkdir($filePath, 0755, true);
        $certPem = $filePath . 'cert.pem';
        !file_exists($certPem) && file_put_contents($certPem, $this->config['cert_pem']);
        $keyPem = $filePath . 'key.pem';
        !file_exists($keyPem) && file_put_contents($keyPem, $this->config['key_pem']);
        return compact('certPem', 'keyPem');
    }




    /**
     * 返回状态给微信服务器
     * @param boolean $return_code
     * @param string $msg
     */
    private function returnCode($return_code = true, $msg = null)
    {
        // 返回状态
        $return = [
            'return_code' => $return_code ? 'SUCCESS' : 'FAIL',
            'return_msg' => $msg ?: 'OK',
        ];
        // 记录日志
        log_write([
            'describe' => '返回微信支付状态',
            'data' => $return
        ]);
        die($this->toXml($return));
    }

    /**
     * 生成paySign
     * @param $nonceStr
     * @param $prepay_id
     * @param $timeStamp
     * @return string
     */
    private function makePaySign($nonceStr, $prepay_id, $timeStamp){
        $data = [
            'appId' => $this->app_id,
            'nonceStr' => $nonceStr,
            'package' => 'prepay_id=' . $prepay_id,
            'signType' => 'MD5',
            'timeStamp' => $timeStamp,
        ];
        // 签名步骤一：按字典序排序参数
        ksort($data);
        $string = $this->toUrlParams($data);
        // 签名步骤二：在string后加入KEY
        $string = $string . '&key=' . $this->apikey;
        // 签名步骤三：MD5加密
        $string = md5($string);
        // 签名步骤四：所有字符转为大写
        $result = strtoupper($string);
        return $result;
    }

    /**
     * 将xml转为array
     * @param $xml
     * @return mixed
     */
    private function fromXml($xml)
    {
        // 禁止引用外部xml实体
        libxml_disable_entity_loader(true);
        return json_decode(json_encode(simplexml_load_string($xml, 'SimpleXMLElement', LIBXML_NOCDATA)), true);
    }

    /**
     * 生成签名
     * @param $values
     * @return string 本函数不覆盖sign成员变量，如要设置签名需要调用SetSign方法赋值
     */
    private function makeSign($values)
    {
        //签名步骤一：按字典序排序参数
        ksort($values);
        $string = $this->toUrlParams($values);
        //签名步骤二：在string后加入KEY
        $string = $string . '&key=' . $this->apikey;
        //签名步骤三：MD5加密
        $string = md5($string);
        //签名步骤四：所有字符转为大写
        $result = strtoupper($string);
        return $result;
    }

    /**
     * 格式化参数格式化成url参数
     * @param $values
     * @return string
     */
    private function toUrlParams($values)
    {
        $buff = '';
        foreach ($values as $k => $v) {
            if ($k != 'sign' && $v != '' && !is_array($v)) {
                $buff .= $k . '=' . $v . '&';
            }
        }
        return trim($buff, '&');
    }

    /**
     * 输出xml字符
     * @param $values
     * @return bool|string
     */
    private function toXml($values)
    {
        if (!is_array($values)
            || count($values) <= 0
        ) {
            return false;
        }

        $xml = "<xml>";
        foreach ($values as $key => $val) {
            if (is_numeric($val)) {
                $xml .= "<" . $key . ">" . $val . "</" . $key . ">";
            } else {
                $xml .= "<" . $key . "><![CDATA[" . $val . "]]></" . $key . ">";
            }
        }
        $xml .= "</xml>";
        return $xml;
    }


    /**
     * 模拟GET请求 HTTPS的页面
     * @param string $url 请求地址
     * @return string $result
     */
    public function get($url){
        $curl = curl_init();
        curl_setopt($curl, CURLOPT_URL, $url);
        curl_setopt($curl, CURLOPT_HEADER, 0);
        curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1);
        curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, FALSE); // https请求 不验证证书和hosts
        $result = curl_exec($curl);
        curl_close($curl);
        return $result;
    }

    /**
     * 模拟POST请求
     * @param $url
     * @param array $data
     * @param bool $useCert
     * @param array $sslCert
     * @return mixed
     */
    public function post($url, $data = [], $useCert = false, $sslCert = []){
        $header = [
            'Content-type: application/json;'
        ];
        $curl = curl_init();
        curl_setopt($curl, CURLOPT_URL, $url);
        curl_setopt($curl, CURLOPT_HTTPHEADER, $header);
        curl_setopt($curl, CURLOPT_HEADER, false);
        curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1);
        curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, false);
        curl_setopt($curl, CURLOPT_POST, TRUE);
        curl_setopt($curl, CURLOPT_POSTFIELDS, $data);
        if ($useCert == true) {
            // 设置证书：cert 与 key 分别属于两个.pem文件
            curl_setopt($curl, CURLOPT_SSLCERTTYPE, 'PEM');
            curl_setopt($curl, CURLOPT_SSLCERT, $sslCert['certPem']);
            curl_setopt($curl, CURLOPT_SSLKEYTYPE, 'PEM');
            curl_setopt($curl, CURLOPT_SSLKEY, $sslCert['keyPem']);
        }
        $result = curl_exec($curl);
        curl_close($curl);
        return $result;
    }

}
